
/* 
Q1: 可否随机一个二进制？
A1：不行，因为随机一个值的话就固定了。

Q2：可否利用源码的和？
A2：不可以，因为加密后无法获得。
注：从这个问题可以看出，加密前面保持不变是很重要的一个特性。

Q3：已知算法是否可以 暴力破解？
A3：可以，因为只有128种可能（单字节仅128种偶性字节）

Q4：密钥长度可以作为分段数。
A4：密钥长度是由分段数决定的，这样避免反推密钥。

奇偶性转换就是一个异或计算:
	0+0=0
	0+1=1
	1+0=1
	1+1=0
*/

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileEncryption {

	public static void main(String[] args) throws Exception {
		if (args.length == 0) {
			args = new String[] { "en", // command
					"C:\\Gitee.com\\high_speed_encryption\\experiment\\key.png", // key
					"C:\\Gitee.com\\high_speed_encryption\\experiment\\FileEncryption.java" }; // input
		}
		run(args);
	}

	public static void run(String[] args) throws Exception {
		String cmd = args.length > 0 ? args[0] : "en";
		String key_file = args.length > 1 ? args[1] : "encrypt.key";
		String input_file = args.length > 2 ? args[2] : "inputfile";
		String output_file = args.length > 3 ? args[3] : "";

		if (args.length == 0) {
			cmd = "en";
			key_file = "";
			input_file = "";
			output_file = "";
		}
		if (cmd == null || !(cmd.equals("en") || cmd.equals("de")))
			throw new Exception("Wrong command: " + cmd);

		if (!new File(key_file).exists())
			throw new Exception("The key file \"" + key_file + "\" does not exist.");

		if (!new File(input_file).exists())
			throw new Exception("The input file \"" + input_file + "\" does not exist.");

		System.out.print("------------------ Arguments Info -----------------\n");
		System.out.println("Cmd:    " + cmd);
		System.out.println("Key:    " + key_file);
		System.out.println("Input:  " + input_file);
		System.out.println("Output: " + output_file);

		FileEncryption fe = new FileEncryption(key_file);
		// 使用密钥对指定的文件加密。
		if (cmd.equals("en")) {
			fe.encrypt(input_file, output_file.length() == 0 ? input_file + ".tmp" : output_file);
		} else if (cmd.equals("de"))
			fe.decrypt(input_file, output_file);
		System.out.println("Done.");
	}

	public static String process(String infile, String outfile, byte[] key, int type) throws Exception {
		// 初始化所有相关变量
		FileInputStream fin = new FileInputStream(infile);
		FileOutputStream fout = new FileOutputStream(outfile);
		long filesize = fin.available();
		byte[] bts = new byte[keysize];
		double t1 = 0, t2 = 0, t3 = 0;
		int readCount = bts.length;
		long timeTick = 0;

		// 加解密过程包括读取、加解密和写回文件。
		while (readCount >= bts.length) {
			// t1: 文件读取时间
			timeTick = System.nanoTime();
			readCount = fin.read(bts, 0, bts.length);
			t1 += System.nanoTime() - timeTick;

			// t2: 文件加密时间
			timeTick = System.nanoTime();
			byte bx = getBX(bts, key, type);
			for (int i = 0; i < readCount; i++)
				bts[i] ^= key[i] ^ bx;
			t2 += System.nanoTime() - timeTick;

			// t3: 文件写入时间
			timeTick = System.nanoTime();
			fout.write(bts, 0, readCount);
			t3 += System.nanoTime() - timeTick;
		}
		fin.close();
		fout.close();

		String datetime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
		appendToFile(datetime, type, "Read", t1, infile, filesize);
		appendToFile(datetime, type, "Process", t2, infile, filesize);
		appendToFile(datetime, type, "Write", t3, infile, filesize);
		appendToFile(datetime, type, "Total", t1 + t2 + t3, infile, filesize);

		return outfile;
	}

	public static boolean appendToFile(String datetime, int type, String item, double time, String infile, long size)
			throws IOException {
		String info = String.format("%s,%d,%s,%.6f,%.6f,%d,%s\n", // 格式化为统一的CSV字符串
				datetime, type, item, // 时间、类型（0=加密，1=解密）和项目名称
				time / 1000000000.0, // 输入时间单位为ns，转换为s
				size * 1000000000.0 / 1073741824.0 / time, size, infile);
		String resfile = "results.csv";
		if (!new File(resfile).exists())
			appendToFile(resfile, "DateTime,Type,Command,Time(s),Speed(GB/s),Flesize(B),Filepath\n");
		appendToFile(resfile, info);
		return true;
	}

	public static void appendToFile(String filepath, String content) throws IOException {
		System.out.print(content);
		FileWriter writer = new FileWriter(filepath, true);
		writer.write(content);
		writer.close();
	}
	
	byte[] keydata;

	public static int keysize = 1048576;

	public FileEncryption() {
	}

	public FileEncryption(String keyfile) throws Exception {
		this.keydata = readBytes(keyfile, keysize);
	}

	public FileEncryption(byte[] keydata) {
		this.keydata = keydata;
	}

	public String encrypt(String input_file, String output_file) throws Exception {
		return FileEncryption.process(input_file, output_file, keydata, 0);
	}

	public String decrypt(String input_file, String output_file) throws Exception {
		if(output_file.length() == 0 && input_file.endsWith(".tmp"))
				output_file = input_file.substring(0, input_file.length() - 4) + ".out";
		return FileEncryption.process(input_file, output_file, keydata, 1);
	}

	static byte[] ones = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4,
			5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2,
			3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4,
			5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
			4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
			6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4,
			5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6,
			7, 5, 6, 6, 7, 6, 7, 7, 8 };

	static int[] map1 = { 0, 0, 0, 3, 0, 5, 6, 192, 0, 9, 10, 96, 12, 160, 192, 15, 0, 17, 18, 48, 20, 80, 96, 23, 24,
			144, 160, 27, 192, 29, 30, 240, 0, 33, 34, 24, 36, 40, 48, 39, 40, 72, 80, 43, 96, 45, 46, 120, 48, 136,
			144, 51, 160, 53, 54, 184, 192, 57, 58, 216, 60, 232, 240, 63, 0, 65, 66, 12, 68, 20, 24, 71, 72, 36, 40,
			75, 48, 77, 78, 60, 80, 68, 72, 83, 80, 85, 86, 92, 96, 89, 90, 108, 92, 116, 120, 95, 96, 132, 136, 99,
			144, 101, 102, 156, 160, 105, 106, 172, 108, 180, 184, 111, 192, 113, 114, 204, 116, 212, 216, 119, 120,
			228, 232, 123, 240, 125, 126, 252, 0, 129, 130, 6, 132, 10, 12, 135, 136, 18, 20, 139, 24, 141, 142, 30,
			144, 34, 36, 147, 40, 149, 150, 46, 48, 153, 154, 54, 156, 58, 60, 159, 160, 66, 68, 163, 72, 165, 166, 78,
			80, 169, 170, 86, 172, 90, 92, 175, 96, 177, 178, 102, 180, 106, 108, 183, 184, 114, 116, 187, 120, 189,
			190, 126, 192, 130, 132, 195, 136, 197, 198, 142, 144, 201, 202, 150, 204, 154, 156, 207, 160, 209, 210,
			166, 212, 170, 172, 215, 216, 178, 180, 219, 184, 221, 222, 190, 192, 225, 226, 198, 228, 202, 204, 231,
			232, 210, 212, 235, 216, 237, 238, 222, 240, 226, 228, 243, 232, 245, 246, 238, 240, 249, 250, 246, 252,
			250, 252, 255 };

	/**
	 * 读取文件的内容作为密钥，如果文件长度大于 等于maxsize，则取maxsize，如果长度小于maxsize，则进行重复读取操作，以满足需求。
	 * 
	 * @param keyfile   密钥文件名
	 * @param keylength 密钥长度
	 * @return
	 * @throws Exception
	 */
	public static byte[] readBytes(String keyfile, int keylength) throws Exception {
		byte[] bts = new byte[keylength];
		int leftBytes = keylength;
		int curPos = 0;

		// 璇诲彇鏂囦欢
		FileInputStream fs = new FileInputStream(keyfile);
		while (true) {
			int readCount = fs.read(bts, curPos, leftBytes);
			if (readCount <= 0)
				break;

			curPos += readCount;
			leftBytes -= readCount;
		}
		fs.close();

		// 对 [p, bts.length) 区间的数据使用 [0, p) 上的数据进行填充。
		if (leftBytes > 0) {
			for (int i = 0; i < leftBytes; i++) {
				bts[curPos + i] = bts[(curPos + i) % curPos];
			}
		}

		return bts;
	}


	/**
	 * 返回一个字节的超奇偶性。
	 * 
	 * @param v
	 * @return
	 */
	public static int getOnes(byte b) {
		return ones[b & 0xff];
	}

	public static boolean isOnesOdd(byte b) {
		return ones[b & 0xff] % 2 == 0;
	}

	public static byte getBX(byte[] bts, byte[] key, int etype) {
		if (bts.length <= 1)
			return 0;

		int offset = 0, bi = 0;
		if (etype == 0) { // encrypt
			for (int i = 0, w = 1; i < 8; i++, w *= 2)
				offset += ones[bts[i] & 0xff] % 2 == 0 ? 0 : w;

			for (int i = 0, w = 1; i < 8; i++, w *= 2)
				bi += (ones[bts[(i + offset) % bts.length] & 0xff]) % 2 == 0 ? 0 : w;

		} else if (etype == 1) {// decrypt
			// 在解密时，需要进行奇偶性转换，这是因为在key的奇偶性会改变data的奇偶性
			// 基于奇偶性变换原则，可以很容易对其进行转换。
			// 加8是为了保证差为正数，这里有一个奇偶性转换
			for (int i = 0, w = 1; i < 8; i++, w *= 2)
				offset += ((ones[bts[i] & 0xff] + 8 - ones[key[i] & 0xff]) % 2) * w;

			for (int i = offset, w = 1; i < offset + 8; i++, w *= 2)
				bi += (ones[bts[i % bts.length] & 0xff] + 8 - ones[key[i % bts.length] & 0xff]) % 2 == 0 ? 0 : w;
		}
		// 转换的目的是为了保证输出的值一定为偶性值，具体实现可以由移动功映射两种方式组成，通过使用表转换可以简化转换过程。
		// byte b = (byte) (bi & 0xff);
		// while (ones[b & 0xff] % 2 > 0)
		// b = (byte) (b << 1);

		return (byte) map1[bi & 0xff];// b;
	}
}
